// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/bitflyer_futures/order_v1/parser.h"

#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::bitflyer_futures::order_v1 {

namespace {

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::ProductPosition;
using coin::proto::SubmitOrderResponse;
using coin2::exchange::base::order::IOrderContext;
using coin2::exchange::bitflyer_futures::symbology::BitflyerCurrency;
using coin2::exchange::bitflyer_futures::symbology::BitflyerFuturesProduct;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline coin::proto::OrderSide GetOrderSide(const std::string& side) {
  if (side == "BUY") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "SELL") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline coin::proto::OrderType GetOrderType(const std::string& type) {
  if (type == "LIMIT") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "MARKET") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void SetOrderStatus(const std::string& status, ProductOrderElement* order) {
  if (status == "REJECTED") {
    order->set_is_live(false);
  } else if (status == "ACTIVE") {
    order->set_is_live(true);
  } else if (status == "CANCELED" || status == "EXPIRED") {
    order->set_cancel_confirmed(true);
  } else if (status == "COMPLETED") {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

inline coin::proto::OrderErrorCode GetOrderErrorCodeFromBitflyerFutures(
    const JsonValue& json_error_msg) {
  using coin::proto::OrderErrorCode;
  auto result = coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR;
  std::string_view error_code(json_error_msg.GetString());

  if (error_code == "200") {
    result = OrderErrorCode::ORDER_NO_ERROR;
  } else if (error_code == "??") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  }

  return result;
}

bool IsBitflyerFuturesErrMessage(const JsonValue& doc) {
  // {"status":-500,"error_message":"Invalid signature","data":null}
  return doc.IsObject() && doc.HasMember("error_message");
}

bool IsOrderUpdate(const JsonValue& json) {
  if (!json.HasMember("method") || !json.HasMember("params") ||
      !json["params"].HasMember("channel")) {
    return false;
  }
  return std::string_view(json["method"].GetString()) == "channelMessage" &&
         std::string_view(json["params"]["channel"].GetString()) == "child_order_events";
}

}  // namespace

bool BitflyerFuturesParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  if (is_futures()) {
    // bitflyer futures
    return ParseRawBalanceFutures(doc, raw_balance_map);
  } else if (is_spot()) {
    // bitflyer spot
    return ParseRawBalanceSpot(doc, raw_balance_map);
  } else {
    NOTREACHED() << "unknown market type. " << JsonToString(doc);
  }
}

AccountBalance BitflyerFuturesParser::ParseFuturesBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Bitflyer");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BitflyerFuturesParser::ParseRawBalanceFutures(
    const JsonValue& doc,
    RawBalanceMap* raw_balance_map) {
  /*
  {
        "collateral": 12796595.800000000000000000000,
        "open_position_pnl": 2062.273340820000000000000000,
        "require_collateral": 2141322.4582744866666666666667,
        "keep_rate": 5.9769877366597985586796229389
  }
  // linchuan: coin1 use getcollateralaccounts, coin2 use getcollateral
  // guess the collateral = JPY + BTC * 0.8
  */
  if (!doc.IsObject() || IsBitflyerFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }
  if (!doc.HasMember("collateral") || !doc.HasMember("require_collateral")) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }

  double total = stod(doc["collateral"].GetString()) + stod(doc["open_position_pnl"].GetString());
  double hold = stod(doc["require_collateral"].GetString());
  double avail = total - hold;

  CurrencyBalance currency_balance;
  currency_balance.set_currency_native("JPY");
  currency_balance.set_available(avail);
  currency_balance.set_hold(hold);
  currency_balance.set_total(total);
  raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);

  return true;
}

AccountBalance BitflyerFuturesParser::ParseSpotBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Bitflyer");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BitflyerFuturesParser::ParseRawBalanceSpot(
    const JsonValue& doc,
    RawBalanceMap* raw_balance_map) {
  /*
  [
    {
      "currency_code": "JPY",
      "amount": 3123724.0,
      "available": 3122693.0
    }, {
      "currency_code": "BTC",
      "amount": 0.60759915,
      "available": 0.60759915
    }, ...
  ]
  */
  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc.GetArray()) {
    double total = stod(data["amount"].GetString());
    double avail = stod(data["available"].GetString());
    double hold = total - avail;

    CurrencyBalance currency_balance;
    currency_balance.set_currency_native(data["currency_code"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_hold(hold);
    currency_balance.set_total(total);
    raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
  }

  return true;
}

void BitflyerFuturesParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BitflyerFuturesParser::ParseBalance(const JsonValue& doc) {
  if (is_futures()) {
    // bitflyer futures
    return ParseFuturesBalance(doc);
  } else if (is_spot()) {
    // bitflyer spot
    return ParseSpotBalance(doc);
  } else {
    NOTREACHED() << "unknown market type. " << JsonToString(doc);
  }
}

void BitflyerFuturesParser::ParseRestAccountPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  ignorable_position_event_ = false;
  auto account_position = ParsePosition(doc);
  if (ignorable_position_event_) {
    result_.set_type(MsgType::IGNORABLE);
    return;
  }
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition BitflyerFuturesParser::ParsePosition(const JsonValue& doc) {
  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return {};
  }

  // update position cache
  for (const auto& [native_symbol, position] : raw_position_map) {
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    position_cache_map_[native_symbol] = position;
  }

  if (position_cache_map_.size() != symcache_->GetProductNative().size()) {
    // not ready for all symbols
    ignorable_position_event_ = true;
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Bitflyer");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    auto* position = acc_position.add_each_position();
    if (position_cache_map_.count(native_symbol) == 0) {
      position->set_long_position(0);
      position->set_short_position(0);
      position->set_net_position(0);
      position->set_available_long_position(0);
      position->set_available_short_position(0);
    } else {
      position->MergeFrom(position_cache_map_.at(native_symbol));
    }
    auto* product = symcache_->GetProductByNative(native_symbol);
    position->set_symbol(product->symbol());
    position->set_native_symbol(product->order_native_symbol());
    DLOG(INFO) << "POS: " << product->symbol() << " " << position->net_position();
  }
  is_account_position_initialized_ = true;

  return acc_position;
}

bool BitflyerFuturesParser::ParseRawPosition(
    const JsonValue& doc,
    RawPositionMap* raw_position_map) {
  /*
  [
    {
      "product_code": "FX_BTC_JPY",
      "side": "SELL",
      "price": 1227501.000000000000,
      "size": 0.573949960000,
      "commission": 0.000000000000,
      "swap_point_accumulate": 0.0,
      "require_collateral": 234841.38328332000000000000000,
      "open_date": "2020-08-27T09:39:00.353",
      "leverage": 3.0,
      "pnl": 395.451522440000000000000000,
      "sfd": 0.000000000000
    },
    ...
  ]
  */
  if (!doc.IsArray() || IsBitflyerFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }

  if (doc.Size() > 0) {
    const std::string native_symbol = doc[0]["product_code"].GetString();
    double long_pos = 0;
    double short_pos = 0;
    for (const auto& data : doc.GetArray()) {
      if (!data.HasMember("side") || !data.HasMember("size")) {
        LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
        return false;
      }
      std::string_view side(data["side"].GetString());
      double size = stod(data["size"].GetString());
      if (side == "BUY") {
        long_pos += size;
      } else if (side == "SELL") {
        short_pos += size;
      } else {
        LOG(ERROR) << "[ParsePosition] Unexpected side! " << JsonToString(data);
        return false;
      }
    }
    if (raw_position_map->count(native_symbol) == 0) {
      raw_position_map->emplace(native_symbol, ProductPosition{});
    }
    auto& position = raw_position_map->at(native_symbol);
    position.set_native_symbol(native_symbol);
    position.set_short_position(long_pos);
    position.set_long_position(short_pos);
    position.set_net_position(long_pos - short_pos);
  }

  return true;
}

AccountPosition BitflyerFuturesParser::ParseSpotPosition(const JsonValue& doc) {
  // same as ParseSpotBalance
  AccountPosition acc_position;
  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return acc_position;
  }

  for (const auto& data : doc.GetArray()) {
    if (std::string_view(data["currency_code"].GetString()) == "BTC") {
      auto* position = acc_position.add_each_position();
      auto* product = symcache_->GetProductByNative("BTC_JPY");
      position->set_symbol(product->symbol());
      position->set_native_symbol(product->order_native_symbol());
      double total = stod(data["amount"].GetString());
      position->set_short_position(0);
      position->set_long_position(total);
      position->set_net_position(total);
    }
  }

  return acc_position;
}

ProductOrderElement BitflyerFuturesParser::ParseRestOrderInfo(const JsonValue& data) {
  ProductOrderElement order;
  order.set_market_type(coin::proto::MarketType::Futures);
  order.set_exchange_type(coin::proto::ExchangeType::Bitflyer);
  order.set_api_version("v1");
  order.set_exchange_order_id(data["child_order_acceptance_id"].GetString());
  order.set_symbol(data["product_code"].GetString());
  order.set_price(stod(data["price"].GetString()));
  order.set_qty(stod(data["size"].GetString()));
  order.set_side(GetOrderSide(data["side"].GetString()));
  order.set_order_type(GetOrderType(data["child_order_type"].GetString()));
  order.set_timestamp(msg_->timestamp);
  SetOrderStatus(data["child_order_state"].GetString(), &order);
  return order;
}

void BitflyerFuturesParser::ParseRestOrdersInfo() {
  /*
  [
    {
      "id": 2101882878,
      "child_order_id": "JFX20200827-095609-967838F",
      "product_code": "FX_BTC_JPY",
      "side": "BUY",
      "child_order_type": "LIMIT",
      "price": 1227230.000000000000,
      "average_price": 1227169.000000000000,
      "size": 0.800000000000,
      "child_order_state": "COMPLETED",
      "expire_date": "2020-08-27T09:57:09",
      "child_order_date": "2020-08-27T09:56:09",
      "child_order_acceptance_id": "JRF20200827-095609-100194",
      "outstanding_size": 0.000000000000,
      "cancel_size": 0.000000000000,
      "executed_size": 0.800000000000,
      "total_commission": 0.000000000000
    }
  }
  */
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!data.IsArray() || IsBitflyerFuturesErrMessage(data)) {
    LOG(ERROR) << "[ParseRestOrderInfoList] " << JsonToString(data);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& info : data.GetArray()) {
    auto* order = product_order_bundle->add_each_order();
    auto new_order = ParseRestOrderInfo(info);
    order->MergeFrom(new_order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

ProductOrderBundle BitflyerFuturesParser::ParseWsOrderInfoListV2(const JsonValue& order_data) {
  /*
  // event_type: ORDER, ORDER_FAILED, CANCEL, CANCEL_FAILED, EXECUTION, EXPIRE
  {
    "jsonrpc": "2.0",
    "method": "channelMessage",
    "params": {
      "channel": "child_order_events",
      "message": [{
        "product_code": "FX_BTC_JPY",
        "child_order_id": "JFX20200902-030013-696501F",
        "child_order_acceptance_id": "JRF20200902-030013-100760",
        "event_date": "2020-09-02T03:00:13.205766Z",
        "event_type": "ORDER",
        "child_order_type": "LIMIT",
        "side": "BUY",
        "price": "1270491",
        "size": "0.34",
        "expire_date": "2020-09-02T03:01:13"
      },
      {
        "product_code": "FX_BTC_JPY",
        "child_order_id": "JFX20200902-030013-696501F",
        "child_order_acceptance_id": "JRF20200902-030013-100760",
        "event_date": "2020-09-02T03:00:13.205766Z",
        "event_type": "EXECUTION",
        "exec_id": "1904629424",
        "side": "BUY",
        "price": "1270491",
        "size": "0.34",
        "commission": "0",
        "sfd": "0"
      },
      {
        "product_code": "FX_BTC_JPY",
        "child_order_id": "JFX20200902-030028-717737F",
        "child_order_acceptance_id": "JRF20200902-030028-159779",
        "event_date": "2020-09-02T03:00:29.8499566Z",
        "event_type": "CANCEL"
      }]
    }
  }
  */
  ProductOrderBundle product_order_bundle;
  if (!order_data.IsObject() || IsBitflyerFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseWsOrderInfoList] " << JsonToString(order_data);
    return product_order_bundle;
  }

  if (!(order_data.HasMember("params") && order_data["params"].HasMember("message") &&
        order_data["params"]["message"].IsArray())) {
    LOG(ERROR) << "Unexpected ws order list format! " << JsonToString(order_data);
    return product_order_bundle;
  }

  for (const auto& data : order_data["params"]["message"].GetArray()) {
    std::string_view event_str(data["event_type"].GetString());
    if (event_str != "EXECUTION") {
      auto* order = product_order_bundle.add_each_order();
      order->set_market_type(coin::proto::MarketType::Futures);
      order->set_exchange_type(coin::proto::ExchangeType::Bitflyer);
      order->set_api_version("v1");
      order->set_exchange_order_id(data["child_order_acceptance_id"].GetString());
      order->set_symbol(data["product_code"].GetString());
      order->set_timestamp(msg_->timestamp);

      if (event_str == "ORDER") {
        order->set_price(stod(data["price"].GetString()));
        order->set_qty(stod(data["size"].GetString()));
        order->set_side(GetOrderSide(data["side"].GetString()));
        order->set_order_type(GetOrderType(data["child_order_type"].GetString()));
        order->set_is_live(true);
      } else if (
          event_str == "ORDER_FAILED" || event_str == "CANCEL_FAILED" || event_str == "EXPIRE") {
        order->set_is_live(false);
      } else if (event_str == "CANCEL") {
        order->set_cancel_confirmed(true);
      }
    }
  }
  return product_order_bundle;
}

void BitflyerFuturesParser::ParseRestFillInfoList() {
  /*
  [
    {
      "id": 1896663785,
      "side": "BUY",
      "price": 1227185.000000000000,
      "size": 0.145618140000,
      "exec_date": "2020-08-27T09:56:09.83",
      "child_order_id": "JFX20200827-095609-967838F",
      "commission": 0.000000000000,
      "child_order_acceptance_id": "JRF20200827-095609-100194"
    }
  ]
  */
  JsonDocument order_data;
  if (!PrepareJsonDocument(&order_data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!order_data.IsArray() || IsBitflyerFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] " << JsonToString(order_data);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : order_data.GetArray()) {
    auto* fill = product_fill_bundle->add_each_fill();
    fill->set_symbol("FX_BTC_JPY");
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(data["child_order_acceptance_id"].GetString());
    fill->set_price(stod(data["price"].GetString()));
    fill->set_qty(stod(data["size"].GetString()));
    fill->set_fill_id(data["id"].GetString());
    fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    product_fill_bundle_found = true;
  }
  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

ProductFillBundle BitflyerFuturesParser::ParseWsFillInfoListV2(const JsonValue& order_data) {
  // same data format as ParseWsOrderInfoList
  ProductFillBundle product_fill_bundle;
  if (!order_data.IsObject() || IsBitflyerFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseWsFillInfoList] " << JsonToString(order_data);
    return product_fill_bundle;
  }

  if (!(order_data.HasMember("params") && order_data["params"].HasMember("message") &&
        order_data["params"]["message"].IsArray())) {
    LOG(ERROR) << "Unexpected ws fill list format! " << JsonToString(order_data);
    return product_fill_bundle;
  }

  for (const auto& data : order_data["params"]["message"].GetArray()) {
    std::string_view event_str(data["event_type"].GetString());
    if (event_str == "EXECUTION") {
      auto* fill = product_fill_bundle.add_each_fill();
      fill->set_symbol(data["product_code"].GetString());
      fill->set_fill_timestamp(msg_->timestamp);
      fill->set_exchange_order_id(data["child_order_acceptance_id"].GetString());
      fill->set_price(stod(data["price"].GetString()));
      fill->set_qty(stod(data["size"].GetString()));
      fill->set_fill_id(data["exec_id"].GetString());
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
  }
  return product_fill_bundle;
}

void BitflyerFuturesParser::ParseSubmitResponse() {
  /*
  {"child_order_acceptance_id": "JRF20150707-050237-639234"}
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BitflyerFuturesSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BitflyerFuturesSubmitOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[BitflyerFuturesSubmitOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (IsBitflyerFuturesErrMessage(json)) {
    auto error_code = GetOrderErrorCodeFromBitflyerFutures(json["error_message"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(json["status"].GetString());
    resp->set_exchange_error_msg(json["error_message"].GetString());
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[BitflyerFuturesSubmitOrder] Error Response" << http_context->res;
    }
    resp->set_success(false);
    return;
  }

  if (!json.HasMember("child_order_acceptance_id")) {
    LOG(ERROR) << "[BitflyerFuturesSubmitOrder] exchange order id not found\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  resp->set_success(true);
  resp->set_exchange_order_id(json["child_order_acceptance_id"].GetString());
}

void BitflyerFuturesParser::ParseCancelResponse() {
  // empty body
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BitflyerFuturesCancelOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BitflyerFuturesCancelOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  if (!std::string(http_context->res.body().data()).empty()) {
    LOG(ERROR) << "[BitflyerFuturesCancelOrder] Not empty body\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  // resp->set_cancel_confirmed(true);
}

void BitflyerFuturesParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsOrderUpdate(document)) {
    *result_.mutable_order_list() = ParseWsOrderInfoListV2(document);
    result_.set_type(MsgType::ORDER_STATUS);
    *result_.mutable_fill_list() = ParseWsFillInfoListV2(document);
    result_.set_type(MsgType::ORDER_FILL);
  }
}

auto BitflyerFuturesParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
    case OrderSubTopicId::REST_ALL_ORDERS:
      ParseRestOrdersInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillInfoList();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
    case OrderSubTopicId::REST_ACCOUNTS:
      ParseRestAccountBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_POSITION:
      ParseRestAccountPosition();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bitflyer_futures::order_v1
